package pl.edu.agh.jemo.evolution.objfunc.impl.clustering;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import pl.edu.agh.jemo.evolution.population.IPopulation;
import pl.edu.agh.jemo.evolution.specimen.ISpecimen;


public class Clusterizer {
	
	IPopulation initPopulation;
	protected int initWindowCount;
	protected List<Double> initWindowSize;
	List<Window> windows;

	protected List<LinkedList<ISpecimen>> sortedPopulations;
	protected Double calculatedMoveTreshold = 0.0;
	protected Double calculatedEnlargeStep = 0.0;
	protected Double calculatedIncreaseThreshold = ClusteringParameters.ENLARGMENT_STOP_THRESHOLD;

	public char[][] operations; 
	
	
	public Clusterizer() {
		windows = new ArrayList<Window>();
	}
	
	private List<List<Double>> getBoundaries() {
		try {
		 return initPopulation.get(0).getGenotype().getBoundaries();
		} catch (ArrayIndexOutOfBoundsException e) {
			System.err.println("Pusta populacja");
			e.printStackTrace();
		}
		return null;
	}	

	void initializeWindows() {
		windows.clear();
		for (int i = 0; i < initWindowCount; i++) {
			Window newWindow = new Window(initWindowSize, getBoundaries());
			List<List<Double>> windowBoundaries = newWindow.getBoundaries();
			
			int lower = find(sortedPopulations.get(0),windowBoundaries.get(0).get(0),0);
			int upper = find(sortedPopulations.get(0),windowBoundaries.get(0).get(1),0);
			newWindow.getSpecimensInside().addAll(sortedPopulations.get(0).subList(lower, upper+1));
			newWindow.getSpecimensBounds().add(new IntPair(lower, upper));
			for (int j = 1 ; j < sortedPopulations.size(); j++) {
				lower = find(sortedPopulations.get(j),windowBoundaries.get(j).get(0),j);
				upper = find(sortedPopulations.get(j),windowBoundaries.get(j).get(1),j);
				newWindow.getSpecimensInside().retainAll(sortedPopulations.get(j).subList(lower, upper+1));
				newWindow.getSpecimensBounds().add(new IntPair(lower, upper));
			}
			newWindow.setSortedPopulations(sortedPopulations);
			if(newWindow.getSpecimensInside().size()>0)
				windows.add(newWindow);
		}
		System.out.println("windowCount po inicjalizacji: " + windows.size());
	}
	
	private void sortPopulation() {
		sortedPopulations = new LinkedList<LinkedList<ISpecimen>>();
		
		for (int i = 0; i < getBoundaries().size(); i++) {
			LinkedList<ISpecimen> population = new LinkedList<ISpecimen>();
			population.addAll(initPopulation);
			Collections.sort(population, new SpecimenComparator(i));
			sortedPopulations.add(population);
		}
	}
	
	public void prepareClusterizer(IPopulation inputPopulation) {
		initPopulation = inputPopulation;
		Double maxRange = 0.0;
		List<List<Double>> problemBounds = initPopulation.get(0).getGenotype().getBoundaries(); 
		for (int i = 0 ; i < problemBounds.size(); i++) {
			maxRange = Math.max(maxRange, problemBounds.get(i).get(1) - problemBounds.get(i).get(0));
		}
		calculatedMoveTreshold = maxRange * ClusteringParameters.MOVEMENT_THRESHOLD;
		calculatedEnlargeStep = maxRange * ClusteringParameters.ENLARGMENT_INCREASE_RATIO;  
		initWindowCount = Math.max(initPopulation.size()/ClusteringParameters.INIT_WINDOW_POPULATION_RATIO, 1);
		initWindowSize = new ArrayList<Double>();
		
		for (List<Double> dim : getBoundaries())
			initWindowSize.add((dim.get(1) - dim.get(0))/Math.pow(initWindowCount, 1.0/getBoundaries().size()));
		
		sortPopulation();
	}
	
	
	public Map<ISpecimen, Window> clusterize(IPopulation inputPopulation) {
		
		prepareClusterizer(inputPopulation);
		initializeWindows();
		moveWindows();
		enlargeWindows();
		mergeWindows();
		return assignMap();
	}

	
	private Map<ISpecimen, Window> assignMap() {
		Map<ISpecimen, Window> retMap = new HashMap<ISpecimen, Window>();
		
		Map<ISpecimen,Integer> maximalClusterSize = new HashMap<ISpecimen, Integer>();
		for (ISpecimen spec: initPopulation)
			maximalClusterSize.put(spec, 0);
		
		for (Window w : windows) {
			//jesli osobnik nalezy do wiecej niz jednego klastra, to wybierany jest najwiekszy klaster
			for (ISpecimen spec : w.getSpecimensInside()) {
				if (maximalClusterSize.get(spec) < w.getSpecimensInside().size()) {
					maximalClusterSize.put(spec, w.getSpecimensInside().size());
					retMap.put(spec, w);
				}
			}
		}
		
		for (ISpecimen spec : initPopulation) {
			if (!retMap.containsKey(spec))
				retMap.put(spec, null);
		}
		
		return retMap;
	}

	public void print2Windows(Window w1, Window w2) {//tymczasowa do wizualizacji
			for(int i=(int) Math.round(getBoundaries().get(0).get(0));i<(int) Math.round(getBoundaries().get(0).get(1));i++) {
				for(int j=(int) Math.round(getBoundaries().get(1).get(0));j<(int) Math.round(getBoundaries().get(1).get(1));j++) {
					//dla kazdego wypisac 1
					w1.getSpecimensInside();
					System.out.print("x ");
				}
				System.out.println();
			}
	}
	
	
	protected void mergeWindows() {
		
		operations = new char[windows.size()][windows.size()];
		for(int k=0;k<windows.size();k++) {windows.get(k).id=k;}
		
		int i=0;
		int j=0;
		while(i<windows.size()-1) {	
			j=i+1;
			while(j<windows.size()) {
				operations[windows.get(i).id][windows.get(j).id] = ' ';
				
				int overlaping = windows.get(i).getOverlapingCount(windows.get(j));
				//jesli sie bardzo pokrywaja to 1 okno wywal
				/*System.out.println("i: " + i + " , j: " + j + " , ovr: " + overlaping);
				System.out.println("sizeI: " + windows.get(i).getSpecimensInside().size() + " , sizeJ: " + windows.get(j).getSpecimensInside().size());
				System.out.println("-");*/
				
				//print2Windows(windows.get(i), windows.get(j));
				
				if(((double)overlaping) / windows.get(i).getSpecimensInside().size() >= ClusteringParameters.MERGE_DISREGARD_RATIO) {
					if(windows.get(i).getSpecimensInside().size() <= windows.get(j).getSpecimensInside().size()) {
						operations[windows.get(i).id][windows.get(j).id] = 'r';
						//usuwanie okna i;
						//System.out.println("remove "+i);
						windows.remove(i);
						i--;
						break;
					}
				} if ((((double)overlaping) / windows.get(i).getSpecimensInside().size() + ((double)overlaping) / windows.get(j).getSpecimensInside().size())/2.0 >=ClusteringParameters.MERGE_RATIO) {
					//merge					
					//System.out.println("merge "+i+" "+j);
					operations[windows.get(i).id][windows.get(j).id] = 'm';
					windows.get(j).getSpecimensInside().addAll(windows.get(i).getSpecimensInside());
					windows.remove(i);
					i--;
					break;
				}
				j++;		
			}
			/*if (j>=windows.size())*/
			i++;
		}
	}

	private void enlargeWindows() {
		for (Window w : windows) {
			w.enlarge(calculatedEnlargeStep, calculatedIncreaseThreshold, calculatedMoveTreshold);
		}
	}

	protected void moveWindows() {
		for (Window w : windows)
			w.move(calculatedMoveTreshold);
	}

	public void setInitWindowCount(int initWindowCount) {
		this.initWindowCount = initWindowCount;
	}

	public int getInitWindowCount() {
		return initWindowCount;
	}

	public void setInitWindowSize(List<Double> initWindowSize) {
		this.initWindowSize = initWindowSize;
	}

	public List<Double> getInitWindowSize() {
		return initWindowSize;
	}
	
	static int find(LinkedList<ISpecimen> pop, Double val, int dim) {  //Zwraca index pierwszy wiekszy (lub rowny)

		if(val<pop.get(0).getGenotype().get(dim))
			return 0;
		else if (val>pop.get(pop.size()-1).getGenotype().get(dim))
			return pop.size()-1;
		
		int a = 0;
		int b = pop.size()-1;
		int mid = 0;
		while ( b - a > 1) {
			mid = (b+a)/2;
			if (pop.get(mid).getGenotype().get(dim) < val)
				a = mid;
			else
				b=mid;
		}			
		return (pop.get(a).getGenotype().get(dim) < val) ? b : a;
	}
	
	public void setWindows(List<Window> windows) {
		this.windows = windows;
	}

}
